##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

include Msf::Auxiliary::Report

class MetasploitModule < Msf::Post

  def initialize(info={})
    super(update_info(info,
        'Name'          => 'Multi Recon Local Exploit Suggester',
        'Description'   => %q{
          This module suggests local meterpreter exploits that can be used. The
          exploits are suggested based on the architecture and platform that
          the user has a shell opened as well as the available exploits in
          meterpreter.

          It's important to note that not all local exploits will be fired.
          Exploits are chosen based on these conditions: session type,
          platform, architecture, and required default options.
        },
        'License'       => MSF_LICENSE,
        'Author'        => [ 'sinn3r', 'Mo' ],
        'Platform'      => all_platforms,
        'SessionTypes'  => [ 'meterpreter', 'shell' ]
    ))

    register_options([
      Msf::OptInt.new('SESSION', [ true, "The session to run this module on." ]),
      Msf::OptBool.new('SHOWDESCRIPTION', [true, "Displays a detailed description for the available exploits", false])
    ],Msf::Post)
  end


  def all_platforms
    Msf::Module::Platform.subclasses.collect {|c| c.realname.downcase }
  end


  def is_module_arch?(mod)
    mod_arch = mod.target.arch || mod.arch
    mod_arch.include?(session.arch)
  end


  def is_module_options_ready?(mod)
    mod.options.each_pair do |option_name, option|
      if option.required && option.default.nil? && mod.datastore[option_name].blank?
        return false
      end
    end

    true
  end


  def is_module_platform?(mod)
    platform_obj = nil
    begin
      platform_obj = Msf::Module::Platform.find_platform(session.platform)
    rescue ArgumentError => e
      # When not found, find_platform raises an ArgumentError
      elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
      return false
    end

    module_platforms = mod.target.platform ? mod.target.platform.platforms : mod.platform.platforms
    module_platforms.include?(platform_obj)
  end


  def is_session_type_compat?(mod)
    mod.session_compatible?(session.sid)
  end


  def set_module_options(mod)
    self.datastore.each_pair do |k,v|
      mod.datastore[k] = v
    end
    if !mod.datastore['SESSION'] && session.present?
      mod.datastore['SESSION'] = session.sid
    end
  end


  def is_module_wanted?(mod)
    (
      mod.kind_of?(Msf::Exploit::Local) &&
      mod.respond_to?(:check) &&
      is_session_type_compat?(mod) &&
      is_module_platform?(mod) &&
      is_module_arch?(mod) &&
      is_module_options_ready?(mod)
    )
  end


  def setup
    print_status "Collecting local exploits for #{session.session_type}..."
    # Initializes an array
    @local_exploits = []
    # Collects exploits into an array
    framework.exploits.each do |name, obj|
      mod = framework.exploits.create(name)
      next unless mod
      set_module_options(mod)
      @local_exploits << mod if is_module_wanted?(mod)
    end
  end


  def show_found_exploits
    if datastore['VERBOSE']
      print_status("The following #{@local_exploits.length} exploit checks are being tried:")
    else
      print_status("#{@local_exploits.length} exploit checks are being tried...")
    end
    @local_exploits.each do |x|
      vprint_status(x.fullname)
    end
  end


  def run
    if @local_exploits.length < 1
      print_error "No suggestions available."
      return
    end

    show_found_exploits
    results = []
    @local_exploits.each do |m|
      begin
        checkcode = m.check
        # See def is_check_interesting?
        if is_check_interesting?(checkcode)
          # Prints the full name and the checkcode message for the exploit
          print_good("#{m.fullname}: #{checkcode.second}")
          results << [m.fullname, checkcode.second]
          # If the datastore option is true, a detailed description will show
          if datastore['SHOWDESCRIPTION']
            # Formatting for the description text
            Rex::Text.wordwrap(Rex::Text.compress(m.description), 2, 70).split(/\n/).each do |line|
              print_line line
            end
          end
        else
          vprint_status("#{m.fullname}: #{checkcode.second}")
        end
      rescue Rex::Post::Meterpreter::RequestError => e
        # Creates a log record in framework.log
        elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
        vprint_error("#{e.class} #{m.shortname} failled to run: #{e.message}")
      end
    end
    report_note(
      :host => rhost,
      :type => "local.suggested_exploits",
      :data => results
    )
  end


  def is_check_interesting?(checkcode)
    [
      Msf::Exploit::CheckCode::Vulnerable,
      Msf::Exploit::CheckCode::Appears,
      Msf::Exploit::CheckCode::Detected
    ].include?(checkcode)
  end


  def print_status(msg='')
    super("#{session.session_host} - #{msg}")
  end


  def print_good(msg='')
    super("#{session.session_host} - #{msg}")
  end


  def print_error(msg='')
    super("#{session.session_host} - #{msg}")
  end
end
